package com.hyt.it.ogt.ks.service.impl;

import com.alibaba.fastjson.JSON;
import com.alibaba.fastjson.JSONArray;
import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper;
import com.baomidou.mybatisplus.core.toolkit.Wrappers;
import com.beust.jcommander.internal.Lists;
import com.google.common.collect.Maps;
import com.hyt.common.apiInfor.ApiResponse;
import com.hyt.core.exception.NestedBusinessException;
import com.hyt.core.util.UUIDUtils;
import com.hyt.it.ogt.ks.config.ConfigManager;
import com.hyt.it.ogt.ks.config.KsBizRunTimeException;
import com.hyt.it.ogt.ks.enums.*;
import com.hyt.it.ogt.ks.feign.KwClient;
import com.hyt.it.ogt.ks.feign.model.*;
import com.hyt.it.ogt.ks.mapper.CandidateProcessMapper;
import com.hyt.it.ogt.ks.mapper.PaperTopicMapper;
import com.hyt.it.ogt.ks.model.dto.InitPaperDataNewStatusDTO;
import com.hyt.it.ogt.ks.model.entity.*;
import com.hyt.it.ogt.ks.model.vo.ExamConfigVO;
import com.hyt.it.ogt.ks.model.vo.InitPaperVO;
import com.hyt.it.ogt.ks.mq.model.CandidateInfoUpdateDTO;
import com.hyt.it.ogt.ks.service.*;
import com.hyt.it.ogt.ks.util.KeyDefineCommon;
import com.hyt.it.ogt.ks.util.StringUtil;
import lombok.extern.slf4j.Slf4j;
import org.apache.commons.lang3.BooleanUtils;
import org.apache.commons.lang3.StringUtils;
import org.springframework.data.redis.core.RedisTemplate;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;

import javax.annotation.Resource;
import java.time.LocalDateTime;
import java.util.*;
import java.util.concurrent.TimeUnit;
import java.util.concurrent.locks.Lock;
import java.util.concurrent.locks.ReentrantLock;
import java.util.stream.Collectors;

/**
 * <p>
 * 初始化试卷
 * </p>
 *
 * @author chengxh
 * @since 2020-06-17
 */
@Slf4j
@Service
public class PaperInitServiceImpl implements IPaperInitService {
    public static final List<String> JUDGEMENT_LIST = Arrays.asList("0,1", "1,0");
    @Resource
    private RedisTemplate redisTemplate;
    @Resource
    private CandidateProcessMapper candidateProcessMapper;
    @Resource
    private IExamCacheService iExamCacheService;
    @Resource
    private ICandidateAnswerService iCandidateAnswerService;
    @Resource
    private IForceItemService iForceItemService;
    @Resource
    private ICandidateProcessService iCandidateProcessService;
    @Resource
    private PaperTopicMapper paperTopicMapper;
    @Resource
    private KwClient kwClient;
    @Resource
    private ICandidatePaperService iCandidatePaperService;
    @Resource
    private ConfigManager configManager;
    @Resource
    private ILogService iLogService;

    @Transactional(rollbackFor = Exception.class)
    @Override
    public InitPaperVO initPaper(String examId, String candidateId) {
        InitPaperVO vo = new InitPaperVO();
        vo.setPaperNone(false);
        vo.setIsGotoThirdCodePaper(false);
        vo.setWebsocket(configManager.getWebsocketUrl());

        // 判断是否已经初始化了或是否需要进行初始化
        InitPaperDataNewStatusDTO ps = this.isInitAndNewPaper(examId, candidateId);
        // 未初始化
        if (ps.getRefreshInitPaper()) {
            //初始化考生答案表结构
            this.initPaper(examId, candidateId, 0, ps);
            Integer paperType = iCandidatePaperService.getFirstPaperType(examId, candidateId);
            vo.setPaperType(paperType);
        } else {
            Integer paperType = iCandidatePaperService.getFirstPaperType(examId, candidateId);
            // 所有试卷已经作答
            if (paperType == null) {
                // 判断是否定制考试（编程题）
                String json = kwClient.getExamCustomizationIsContainCodingPaper(examId);
                ApiResponse<Boolean> apiResponse = ApiResponse.ConvertRet(json, Boolean.class);
                if (apiResponse.getData()) {
                    // 考试包含编程题
                    vo.setIsGotoThirdCodePaper(true);
                    iCandidateProcessService.change2Testing(examId, candidateId);
                } else {
                    // 非定制化考试，进入这里则属于异常情况，提示已经作答完成
                    throw new KsBizRunTimeException(BizStatusCode.HAD_FINISH_EXAM.getCode());
                }
            } else {
                // 返回第一套试卷类型
                vo.setPaperType(paperType);
            }
        }

        // 增加登录次数记录
        CandidateProcess one = iCandidateProcessService.lambdaQuery()
                .select(CandidateProcess::getId, CandidateProcess::getLoginCount)
                .eq(CandidateProcess::getExamId, examId)
                .eq(CandidateProcess::getCandidateId, candidateId).one();
        Integer lc = one.getLoginCount();
        if (lc == null) {
            lc = 0;
        }
        List<ExamConfigVO> configList = iExamCacheService.getExamConfigCache(examId);
        Optional<ExamConfigVO> loginCountConfig = configList.stream().filter(l -> l.getConfigCode()
                .equals(ExamConfigCode.EXAM_CONFIG_CODE_LOGIN_COUNT.getCode())).findFirst();
        if (loginCountConfig.isPresent()) {
            int loginCountLimit = Integer.valueOf(loginCountConfig.get().getConfigValue());
            if (loginCountLimit > 0 && one.getLoginCount() >= loginCountLimit) {
                throw new KsBizRunTimeException(BizStatusCode.OUT_LOGIN_LIMIT.getCode());
            } else {
                lc = lc + 1;
                iCandidateProcessService.lambdaUpdate().set(CandidateProcess::getLoginCount, lc)
                        .eq(CandidateProcess::getId, one.getId()).update();
                // 更新kw登录次数
                kwClient.updateLoginCount(candidateId, lc);
            }
        }
        return vo;
    }


    @Transactional(rollbackFor = Exception.class)
    @Override
    public void initPaper(String examId, String candidateId, Integer answerVer, InitPaperDataNewStatusDTO ps) {
        String key = KeyDefineCommon.getPaperInitKey(examId, candidateId);
        try {
            // 如果为空就set值，并返回1(true), 如果存在(不为空)不进行操作，并返回0(false)
            Boolean flag = redisTemplate.opsForValue().setIfAbsent(key, true, 2, TimeUnit.MINUTES);
            if (flag) {
                this.exec(examId, candidateId, answerVer,ps);
            }
        } finally {
            redisTemplate.delete(key);
        }
    }

    /**
     * 多线程使用初始化试装，向上抛出异常，便与线程池记录不成功的任务
     *
     * @param examId      :  考试id
     * @param candidateId :  生生id
     * @param answerVer   :
     */
    @Transactional(rollbackFor = Exception.class)
    @Override
    public void initPaperForTask(String examId, String candidateId, String admissionNumber, Integer answerVer) {
        String key = KeyDefineCommon.getPaperInitKey(examId, candidateId);
        try {
            if (redisTemplate.opsForValue().setIfAbsent(key, true, 20, TimeUnit.SECONDS)) {
                //调试代码用的 -- 上线一定要注掉
                //TimeUnit.SECONDS.sleep(12);
                this.execForTask(examId, candidateId, admissionNumber, answerVer);
                log.info("--------------------- 初始化线程走完了-----------------");
            }
        } catch (Exception e) {
            log.error("初始化考生试卷信息异常，线程id:【{}】,考生id:【{}】,考生id：【{}】,异常原因:{}",Thread.currentThread().getName(),examId,candidateId,e);
            //throw e;
        } finally {
            redisTemplate.delete(key);
            kwClient.addInitNum(examId,1);
            log.info("---------------------初始化线程走完了调用kw增加1 -----------------");
        }
    }

    private void execForTask(String examId, String candidateId, String admissionNumber, Integer answerVer) {
        CandidateProcess candidateProcess = iCandidateProcessService.getOne(Wrappers.<CandidateProcess>lambdaQuery()
                .eq(CandidateProcess::getExamId, examId).eq(CandidateProcess::getCandidateId, candidateId)
                .last("limit 1"));
        boolean candidateProcessExist = false;
        boolean refreshInitPaper = false;
        //现考现评时不进行初始化
        String isCurrentStr = kwClient.queryIsCurrentExam(examId);
        log.info("调用kw判断当前考试是否为现考现评，返回的响应数据为:【{}】,考试id为【{}】",isCurrentStr,examId);
        ApiResponse<Boolean> isCurrentRes = ApiResponse.ConvertRet(isCurrentStr,Boolean.class);
        if(!Objects.equals(isCurrentRes.getCode(),BizStatusCode.KW_SUCCESS_OK.getCode())){
            log.error("调用kw判断当前考试是否为现考现评，返回的响应数据为:【{}】,考试id为【{}】",isCurrentStr,examId);
            throw new KsBizRunTimeException(BizStatusCode.API_KW_CURRRENT_EXAM_ERROR.getCode());
        }
        if(Objects.equals(Boolean.TRUE,isCurrentRes.getData())){
            log.info("当前考试为现考现评类型，不需要进行试卷初始化操作！，考试id：【{}】",examId);
            return;
        }
		// 重新获取考试绑定的随机一套试卷，并把考生试卷信息放缓存
        CandidatePaperInfo candidatePaperInfo = iExamCacheService.getNewestPaperInfo(examId, candidateId);

        if (candidatePaperInfo == null || candidatePaperInfo.getPaperList() == null
                || candidatePaperInfo.getPaperList().size() == 0) {
            log.error("从考务或者缓存获取考生绑定的试卷信息");
            throw new NestedBusinessException(String.valueOf(BizStatusCode.GET_CADIDATE_PAPER_NONE.getCode()));
        }

        if (Objects.nonNull(candidateProcess)) {
            candidateProcessExist = true;
            boolean isStarted = candidateProcess.getStartedExam();
            //查询是否答题
            LambdaQueryWrapper<CandidateAnswer> answerQuery = Wrappers.<CandidateAnswer>lambdaQuery()
                    .eq(CandidateAnswer::getExamId, examId).eq(CandidateAnswer::getCandidateId, candidateId)
                    .eq(CandidateAnswer::getDelFlag, Boolean.FALSE).ne(CandidateAnswer::getObjectiveAnswer, null)
                    .ne(CandidateAnswer::getSubjectiveAnswer, null);
            int count = Optional.ofNullable(iCandidateAnswerService.count(answerQuery)).orElse(0);
            if (Objects.equals(Boolean.TRUE, isStarted) || count > 0) {
                log.info("考生已经作答，不进行试卷初始化,考试id:{},考生id:{}", examId, candidateId);
                return;
            }
            refreshInitPaper = true;
            if(Objects.equals(candidatePaperInfo.getPaperId(),candidateProcess.getPaperId()) &&
               Objects.equals(candidatePaperInfo.getPaperVersion(),candidateProcess.getPaperVersion())){
                log.info("考生初始化的考试试卷信息已是最新，不再进行初始化了,考试id:{},考生id:{},试卷id：{}，试卷version:{}",
                        examId, candidateId,candidatePaperInfo.getPaperId(),candidatePaperInfo.getPaperVersion());
                return;
            }
        }
        // 清楚数据，重新绑定试卷
        if(refreshInitPaper){
            // 2021.11.01 改成换了试卷之后先清理数据，所以这里不需要再去清理了
            this.deleteOldCandidatePaperData(examId, candidateId);
        }

        for (int i = 0; i < candidatePaperInfo.getPaperList().size(); i++) {
            Paper paper = candidatePaperInfo.getPaperList().get(i);
            // 保存考生小题
            this.initCandidateAnswerData(examId, candidateId, admissionNumber,
                    paper.getPaperId(), paper.getPaperVersion(), answerVer, i + 1);
        }
        //更改考生过程表
        if (candidateProcessExist) {
            this.saveCandidateProcess(examId, candidateId, candidatePaperInfo);
        } else {
            this.saveCandidateProcessInitData(examId, candidateId, candidatePaperInfo,admissionNumber);
        }
        // 通知考务更新考生信息
        iCandidateProcessService.updateKwCandidateInfo(CandidateInfoUpdateDTO.builder().examId(examId)
                .candidateId(candidateId).paperId(candidatePaperInfo.getPaperId())
                .paperVersion(candidatePaperInfo.getPaperVersion()).build());

        //iCandidateLogService.saveLog(examId, null, candidateId, CandidateLogOperateType.INIT_PAPER.getCode(), null);
    }

    private void exec(String examId, String candidateId, Integer answerVer, InitPaperDataNewStatusDTO ps) {
        CandidateProcess candidateProcess = ps.getCandidateProcess();
        //现考现评时不进行初始化
        String isCurrentStr = kwClient.queryIsCurrentExam(examId);
        log.info("调用kw判断当前考试是否为现考现评，返回的响应数据为:【{}】,考试id为【{}】",isCurrentStr,examId);
        ApiResponse<Boolean> isCurrentRes = ApiResponse.ConvertRet(isCurrentStr,Boolean.class);
        if(!Objects.equals(isCurrentRes.getCode(),BizStatusCode.KW_SUCCESS_OK.getCode())){
            log.error("调用kw判断当前考试是否为现考现评，返回的响应数据为:【{}】,考试id为【{}】",isCurrentStr,examId);
            throw new KsBizRunTimeException(BizStatusCode.API_KW_CURRRENT_EXAM_ERROR.getCode());
        }
        if(Objects.equals(Boolean.TRUE,isCurrentRes.getData())){
            log.info("当前考试为现考现评类型，不需要进行试卷初始化操作！，考试id：【{}】",examId);
            return;
        }
        boolean refreshInitPaper = false;
        //当调用方法没有传入是否需要重新初始化对象时，需要调用
        if(Objects.isNull(ps)){
            ps = this.isInitAndNewPaper(examId,candidateId);
        }
        refreshInitPaper = ps.getRefreshInitPaper();
        CandidatePaperInfo candidatePaperInfo = null;
        if (refreshInitPaper) {
            // 重新获取考试绑定的随机一套试卷，并把考生试卷信息放缓存
            candidatePaperInfo = iExamCacheService.getNewestPaperInfo(examId, candidateId);
        } else {
            //从考务或者缓存获取考生绑定的试卷信息
            candidatePaperInfo = iExamCacheService.getCandidatePaperInfoCache(examId, candidateId);
        }
        if (candidatePaperInfo == null || candidatePaperInfo.getPaperList() == null
                || candidatePaperInfo.getPaperList().size() == 0) {
            log.error("从考务或者缓存获取考生绑定的试卷信息");
            throw new KsBizRunTimeException(BizStatusCode.GET_CADIDATE_PAPER_NONE.getCode());
        }

        if (refreshInitPaper) {
            // 清楚数据，重新绑定试卷
            this.deleteOldCandidatePaperData(examId, candidateId);
        }

        for (int i = 0; i < candidatePaperInfo.getPaperList().size(); i++) {
            Paper paper = candidatePaperInfo.getPaperList().get(i);
            // 保存考生小题
            this.initCandidateAnswerData(examId, candidateId, ps.getCandidateProcess().getAdmissionNumber(),
                    paper.getPaperId(), paper.getPaperVersion(), answerVer, i + 1);
        }
        //更改考生过程表，变为考中状态
        this.saveCandidateProcess(examId, candidateId, candidatePaperInfo);
        // 通知考务更新考生信息
        iCandidateProcessService.updateKwCandidateInfo(CandidateInfoUpdateDTO.builder().examId(examId)
                .candidateId(candidateId).paperId(candidatePaperInfo.getPaperId())
                .paperVersion(candidatePaperInfo.getPaperVersion()).build());

        //iCandidateLogService.saveLog(examId, null, candidateId, CandidateLogOperateType.INIT_PAPER.getCode(), null);
    }

    @Override
    public InitPaperDataNewStatusDTO isInitAndNewPaper(String examId, String candidateId){
        InitPaperDataNewStatusDTO status = new InitPaperDataNewStatusDTO();
        try {
            //考生过程表
            CandidateProcess candidateProcess = iCandidateProcessService.getOne(Wrappers.<CandidateProcess>lambdaQuery()
                    .select(CandidateProcess::getAdmissionNumber, CandidateProcess::getPaperId,
                            CandidateProcess::getPaperVersion, CandidateProcess::getStartedExam)
                    .eq(CandidateProcess::getExamId, examId)
                    .eq(CandidateProcess::getCandidateId, candidateId).last("limit 1"));
            status.setCandidateProcess(candidateProcess);
            //情况一：当前有考试过程表且考生过程表中有试卷id

            if(Objects.nonNull(candidateProcess) && StringUtils.isNotBlank(candidateProcess.getPaperId())){
                // 判断是否是当前绑定的试卷或试卷包，且没进入过作答界面，不是则需要重新初始化
                if (candidateProcess.getStartedExam() == null || !candidateProcess.getStartedExam()) {
                    //考生没有作答，还要去判断试卷是否是最新的
                    String res = kwClient.checkIsCurrentPaper(examId, candidateProcess.getPaperId(),
                            candidateProcess.getPaperVersion());
                    ApiResponse response = JSON.parseObject(res, ApiResponse.class);
                    if(Objects.equals(BizStatusCode.KW_SUCCESS_OK.getCode(),response.getCode())){
                        if(Objects.equals(Boolean.TRUE, BooleanUtils.toBoolean(String.valueOf(response.getData())))){
                            //考生没有作答，但已经是最新试卷不需要重初始化
                            status.setRefreshInitPaper(Boolean.FALSE);
                            return status;
                        } else {
                            status.setRefreshInitPaper(Boolean.TRUE);
                            return status;//不是最新试卷
                        }
                    }
                } else {
                    //考生有作答了，不能再初始化了
                    status.setRefreshInitPaper(Boolean.FALSE);
                    return status;
                }
            }

            status.setRefreshInitPaper(Boolean.TRUE);
            return status;
        } catch (Exception e){
            log.error("判断当前考生是否需要初始化试卷发生异常，考生id：{},考试id:{},异常信息为:【{}】",candidateId,examId,e);
            return status;
        }
    }

    /**
     * 性格测试卷：大题乱序（需要配置小题乱序），小题和选项都不乱序
     * 普通卷：大题、小题和选项乱序都根据配置来，但如配置题型排除，该题型的小题和选项都不乱序（复合题下小题和题选项也不乱序）
     * @param examId 考试ID
     * @param candidateId 考生ID
     * @param admissionNumber 准考证
     * @param paperId 试卷ID
     * @param paperVersion 试卷版本号
     * @param answerVer 答案版本号（主要是不满意重做功能要区分版本号，其他场景版本号都是1）
     * @param sort 第几套试卷
     */
    public void initCandidateAnswerData(String examId, String candidateId, String admissionNumber, String paperId,
                                        String paperVersion, Integer answerVer, int sort) {
        //从题库或者缓存获取试卷信息
        PaperDataVO paperDataVO = iExamCacheService.getPaperInfoCache(paperId, paperVersion);
        if (paperDataVO == null) {
            log.error("题库或者缓存都没有该试卷: {},{}", paperId, paperVersion);
        }
        //获取考务乱序配置项
        Map<String, String> mixConfig = this.getMixConfig(examId);
        // 是否小题乱序，性格测试卷小题不乱序
        boolean itemMixFlag = mixConfig.containsKey(ExamConfigCode.EXAM_CONFIG_QUESTIONS_DISORDERS_ET.getCode());
        // 是否选项乱序，性格测试卷选项不乱序
        boolean optionMixFlag = mixConfig.containsKey(ExamConfigCode.EXAM_CONFIG_OPTIONS_DISORDER_SET.getCode())
                && PaperTypeCode.NORMAL.getCode().intValue() == paperDataVO.getPaperType().intValue();
        // 开启乱序排除题型
        boolean disorderItemFlag = mixConfig.containsKey(ExamConfigCode.EXAM_CONFIG_CODE_DISORDER_SET_EXCLUDE_ITEM_TYPE.getCode());
        // 是否性格测试卷
        boolean isCharacterPaper = PaperTypeCode.CHARACTER.getCode().intValue() == paperDataVO.getPaperType().intValue();
        // 大题乱序设置
        boolean topicMixFlag = mixConfig.containsKey(ExamConfigCode.EXAM_CONFIG_TOPIC_DISORDER.getCode());

        // 保存考生试卷
        String candidatePaperId = this.saveCandidatePaper(examId, candidateId, admissionNumber, paperDataVO, sort);
        // 保存迫选项和选项关系
        List<ForceItem> forceItemList = new ArrayList<>();
        // 保存所有小题，包含复合题，复合题子小题
        List<CandidateAnswer> candidateAnswerList = new ArrayList<CandidateAnswer>();
        // 大题列表
        List<PaperTopic> topicList = new ArrayList<>();
        // 小题乱序排除的题型（即使勾上选项乱序，选项也不参与乱序）,只针对二级题
        List<String> excludeDisorderItemTypeList = new ArrayList<>();
        if(disorderItemFlag) {
            String s = mixConfig.get(ExamConfigCode.EXAM_CONFIG_CODE_DISORDER_SET_EXCLUDE_ITEM_TYPE_LIST.getCode());
            if(StringUtils.isNotBlank(s) && s.length() > 0) {
                String[] arr = s.split(",");
                excludeDisorderItemTypeList.addAll(Lists.newArrayList(arr));
            }
        }
        // 遍历大题
        for (int i = 0; i < paperDataVO.getTopicDataList().size(); i++) {
            TopicDataVO topicDataVO = paperDataVO.getTopicDataList().get(i);
            // 小题乱序排除的题（即使勾上选项乱序，选项也不参与乱序）
            Map<Integer, CandidateAnswer> excludeDisorderItemMap = Maps.newHashMap();
            // 记录一个大题内的所有小题，如果配置乱序，直接用这个数据乱序
            List<CandidateAnswer> answerList = new ArrayList<CandidateAnswer>();
            // 遍历小题
            topicDataVO.getItemDataList().forEach(itemDataVO -> {
                CandidateAnswer candidateAnswer = null;
                // 该题型不参与乱序（包括选项）
                boolean excludeItemFlag = excludeDisorderItemTypeList.contains(itemDataVO.getItemType());

                if (PaperItemTypeCode.ITEM_TYPE_COMPOUND_CODE.getCode().equalsIgnoreCase(itemDataVO.getItemType()) ||
                        PaperItemTypeCode.ITEM_TYPE_CASE_ANALYSIS_CODE.getCode().equalsIgnoreCase(itemDataVO.getItemType()) ||
                        PaperItemTypeCode.ITEM_TYPE_MATCHING_CODE.getCode().equalsIgnoreCase(itemDataVO.getItemType()) ||
                        PaperItemTypeCode.ITEM_TYPE_CLOZE_FILL_IN_BLANKS_CODE.getCode().equalsIgnoreCase(itemDataVO.getItemType())) {
                    // 一个复合题下所有小题集合，方便子小题乱序
                    List<CandidateAnswer> childList = new ArrayList<CandidateAnswer>();
                    itemDataVO.getChildItemList().forEach(childItemDataVO -> {
                        // 选项固定不乱序
                        childList.add(this.handleItem(examId, candidateId, admissionNumber, paperId, paperVersion,
                                answerVer, childItemDataVO, Boolean.FALSE, topicDataVO, forceItemList, candidateAnswerList));
                    });
                    // 子小题固定不乱序
                    this.mixItem(childList, Boolean.FALSE, null);
                    candidateAnswerList.addAll(childList);
                    candidateAnswer = this.handleItem(examId, candidateId, admissionNumber, paperId, paperVersion,
                            answerVer, itemDataVO, Boolean.FALSE, topicDataVO, forceItemList, candidateAnswerList);
                } else {
                    // 其他题型，如单选、多选、简答等
                    candidateAnswer = this.handleItem(examId, candidateId, admissionNumber, paperId, paperVersion,
                            answerVer, itemDataVO, optionMixFlag, topicDataVO, forceItemList, candidateAnswerList);
                }
                
                if (excludeItemFlag) {
                    // 不参与乱序的题不放到列表中，后面排序再
                    excludeDisorderItemMap.put(itemDataVO.getItemSort(), candidateAnswer);
                } else {
                    // 二级题
                    answerList.add(candidateAnswer);
                }
            });
            // 二级小题乱序，性格测试卷大题内小题也乱序
            this.mixItem(answerList, itemMixFlag, excludeDisorderItemMap);
            candidateAnswerList.addAll(answerList);
            //保存大题表
            topicList.add(this.createPaperTopic(candidatePaperId, i + 1, topicDataVO.getTopicId()));
        }
        //性格测试卷，考试配置小题乱序，则大题乱序，但是选项不会乱序；普通卷根据配置来判断是否大题乱序
        if ((!isCharacterPaper && topicMixFlag) || (isCharacterPaper && topicList.size() > 0 && itemMixFlag)) {
            // 大题乱序
            this.mixTopic(topicList);
        }
        // 重新定义数据集
        List<CandidateAnswer> answerList = new ArrayList<CandidateAnswer>();

        // 大题数据入库
        topicList.stream().forEach(item -> {
            paperTopicMapper.insert(item);
            // 小题按照大题重新排，之所以这么做是因为性格测试卷需要从第一道大题开始排小题号，大题间小题号需要连续
            List<CandidateAnswer> collect = candidateAnswerList.stream()
                    .filter(answer -> item.getTopicId().equals(answer.getTopicId()))
                    .collect(Collectors.toList());
            answerList.addAll(collect);
        });

        // 迫选项入库
        if (forceItemList.size() > 0) {
            // 保存迫选项对应关系表
            iForceItemService.saveBatch(forceItemList);
        }
        if (answerList.size() > 0) {
            // 性格测试卷，按试卷在从1开始排题号，不按大题从1排号
            if (isCharacterPaper) {
                for (int i = 0; i < answerList.size(); i++) {
                    answerList.get(i).setItemMixSort(i + 1);
                }
            }
            //插入答案表
            iCandidateAnswerService.saveBatch(answerList);
        }
    }


    private void deleteOldCandidatePaperData(String examId, String candidateId) {
        iCandidateAnswerService.remove(Wrappers.<CandidateAnswer>lambdaQuery().eq(CandidateAnswer::getExamId, examId)
                .eq(CandidateAnswer::getCandidateId, candidateId));
        iCandidatePaperService.remove(Wrappers.<CandidatePaper>lambdaQuery().eq(CandidatePaper::getExamId, examId)
                .eq(CandidatePaper::getCandidateId, candidateId));
        iForceItemService.remove(Wrappers.<ForceItem>lambdaQuery().eq(ForceItem::getExamId, examId)
                .eq(ForceItem::getCandidateId, candidateId));

    }

    private PaperTopic createPaperTopic(String candidatePaperId, int sort, String topicId) {
        PaperTopic paperTopic = new PaperTopic();
        paperTopic.setAnswer(false);
        paperTopic.setCandidatePaperId(candidatePaperId);
        paperTopic.setSort(sort);
        paperTopic.setTopicId(topicId);
        return paperTopic;
    }

    /**
     * 大题乱序
     *
     * @param topicList
     */
    private void mixTopic(List<PaperTopic> topicList) {
        Collections.shuffle(topicList);
        for (int i = 0; i < topicList.size(); i++) {
            topicList.get(i).setSort(i + 1);
        }
    }

    /**
     * 一道大题内所有小题乱序
     * @param candidateAnswerList 乱序题集合
     * @param flag 是否乱序
     * @param excludeDisorderItemMap 不参与乱序题集合
     */
    private void mixItem(List<CandidateAnswer> candidateAnswerList, boolean flag, Map<Integer, CandidateAnswer> excludeDisorderItemMap) {
        if (candidateAnswerList.size() > 0) {
            if (flag) {
                // 乱序
                Collections.shuffle(candidateAnswerList);
            }
            int n = 1;
            for (int i = 0; i < candidateAnswerList.size(); i++) {
                CandidateAnswer itemMix = candidateAnswerList.get(i);
                if(excludeDisorderItemMap != null) {
                    while (excludeDisorderItemMap.containsKey(n)) {
                        ++n;
                    }
                }
                itemMix.setItemMixSort(n);
                ++n;
            }
        }
        if(excludeDisorderItemMap != null && excludeDisorderItemMap.size() > 0) {
            excludeDisorderItemMap.forEach((k, v) -> {
                candidateAnswerList.add(v);
            });
        }
    }

    /**
     * 选项乱序
     *
     * @param itemDataVO
     * @param candidateAnswer
     */
    private void mixOption(ItemDataVO itemDataVO, CandidateAnswer candidateAnswer, boolean flag) {
        List<String> optionMixList = new ArrayList<>();
        if (PaperItemTypeCode.ITEM_TYPE_JUDGMENT_CODE.getCode().equalsIgnoreCase(itemDataVO.getItemType())) {
            optionMixList.add("0");
            optionMixList.add("1");
            // 判断题不乱序，特殊处理
            flag = false;
        } else {
            if (itemDataVO.getOptionsDataList() != null && itemDataVO.getOptionsDataList().size() > 0) {
                itemDataVO.getOptionsDataList().stream().forEach(item -> {
                    optionMixList.add(item.getOptionId());
                });
            }
        }
        if (optionMixList.size() > 0) {
            if (flag) {
                // 乱序
                Collections.shuffle(optionMixList);
            }
            candidateAnswer.setOptionMixSortRule(StringUtils.join(optionMixList, ","));
        }
    }

    /**
     * 获取考务乱序配置 todo 加缓存
     *
     * @param examId
     * @return
     */
    private Map<String, String> getMixConfig(String examId) {
        int n = 0;
        //获取考务配置项
        String repExamConfigRtn = kwClient.getExamConfig(examId);
        ApiResponse<JSONArray> resExamConfig = JSON.parseObject(repExamConfigRtn, ApiResponse.class);
        List<ExamConfigVO> examConfigList = JSON.parseArray(resExamConfig.getData().toJSONString(), ExamConfigVO.class);
        //提取乱序配置code
        Map<String, String> mixCode = new HashMap<>();
        examConfigList.stream().forEach(examConfig -> {
            String configCode = examConfig.getConfigCode();
            String configValue = examConfig.getConfigValue();
            if (ExamConfigCode.EXAM_CONFIG_QUESTIONS_DISORDERS_ET.getCode().equalsIgnoreCase(configCode)
                    || ExamConfigCode.EXAM_CONFIG_OPTIONS_DISORDER_SET.getCode().equalsIgnoreCase(configCode)
                    || ExamConfigCode.EXAM_CONFIG_TOPIC_DISORDER.getCode().equalsIgnoreCase(configCode)
                    || ExamConfigCode.EXAM_CONFIG_CODE_DISORDER_SET_EXCLUDE_ITEM_TYPE.getCode().equalsIgnoreCase(configCode)
                    && "true".equalsIgnoreCase(configValue)) {
                mixCode.put(configCode, configValue);
            } else if (ExamConfigCode.EXAM_CONFIG_CODE_DISORDER_SET_EXCLUDE_ITEM_TYPE_LIST.getCode().equalsIgnoreCase(configCode)) {
                mixCode.put(ExamConfigCode.EXAM_CONFIG_CODE_DISORDER_SET_EXCLUDE_ITEM_TYPE_LIST.getCode(), configValue);
            }
        });
        return mixCode;
    }

    private CandidateAnswer handleItem(String examId, String candidateId, String admissionNumber, String paperId,
                                       String paperVersion, Integer answerVer, ItemDataVO itemDataVO, boolean optionMixFlag,
                                       TopicDataVO topicDataVO, List<ForceItem> forceItemList, List<CandidateAnswer> candidateAnswerList) {
        // 小题信息
        String answer = itemDataVO.getAnswer();
        if (PaperItemTypeCode.ITEM_TYPE_MULTIPLE_CODE.getCode().equalsIgnoreCase(itemDataVO.getItemType())
            ||PaperItemTypeCode.ITEM_TYPE_INDEFINITE_CODE.getCode().equalsIgnoreCase(itemDataVO.getItemType())) {
            String[] answersAry = answer.split(",");
            Arrays.sort(answersAry);
            answer = StringUtils.join(answersAry, ",");
        }
        CandidateAnswer candidateAnswer = this.setBaseCandidateAnswer(examId, candidateId, admissionNumber,
                itemDataVO.getItemId(), itemDataVO.getItemPid(), itemDataVO.getScore(), answer, itemDataVO);
        candidateAnswer.setPaperId(paperId);
        candidateAnswer.setPaperVersion(paperVersion);
        candidateAnswer.setAnswerVer(answerVer);
        candidateAnswer.setItemMixSort(itemDataVO.getItemSort());
        if (PaperItemTypeCode.ITEM_TYPE_JUDGMENT_CODE.getCode().equalsIgnoreCase(itemDataVO.getItemType())) {
            // 判断题不乱序
            String pdMix = "1,0";
            if(optionMixFlag) {
                pdMix = JUDGEMENT_LIST.get(new Random().nextInt(2));
            }
            candidateAnswer.setOptionMixSortRule(pdMix);
        } else if (PaperItemTypeCode.ITEM_TYPE_MULTIPLE_CODE.getCode().equalsIgnoreCase(itemDataVO.getItemType()) ||
                PaperItemTypeCode.ITEM_TYPE_FORCE_CHOICE_CODE.getCode().equalsIgnoreCase(itemDataVO.getItemType()) ||
                PaperItemTypeCode.ITEM_TYPE_SINGLE_CODE.getCode().equalsIgnoreCase(itemDataVO.getItemType()) ||
                PaperItemTypeCode.ITEM_TYPE_WEIGHT_CODE.getCode().equalsIgnoreCase(itemDataVO.getItemType())) {
            // 多选题和单选题，选项乱序
            this.mixOption(itemDataVO, candidateAnswer, optionMixFlag);
            if (PaperItemTypeCode.ITEM_TYPE_FORCE_CHOICE_CODE.getCode().equalsIgnoreCase(itemDataVO.getItemType())) {
                // 保存迫选项和选项之间的关系，只支持性格测试卷
                this.setForceItem(examId, candidateId, paperId, paperVersion, topicDataVO, itemDataVO, forceItemList);
            }
        } else if (PaperItemTypeCode.ITEM_TYPE_FILL_IN_BLANKS_CODE.getCode().equalsIgnoreCase(itemDataVO.getItemType())) {
            // 填空题可以看作有子小题
            itemDataVO.getOptionsDataList().forEach(optionsDataVO -> {
                CandidateAnswer ca = this.setBaseCandidateAnswer(examId, candidateId, admissionNumber,
                        optionsDataVO.getOptionId(), itemDataVO.getItemId(),
                        itemDataVO.getScore(), optionsDataVO.getOptionContent(), itemDataVO);
                ca.setPaperId(paperId);
                ca.setPaperVersion(paperVersion);
                ca.setAnswerVer(answerVer);
                // 填空题天空项不参与不乱序，但是填空题本身会参与乱序
                candidateAnswerList.add(ca);
            });
        } else if (PaperItemTypeCode.ITEM_TYPE_INDEFINITE_CODE.getCode().equalsIgnoreCase(itemDataVO.getItemType())){
            //不定项选择题暂不参加选项乱序
            this.mixOption(itemDataVO, candidateAnswer, optionMixFlag);
        } else if (PaperItemTypeCode.ITEM_TYPE_MATCHING_CODE.getCode().equalsIgnoreCase(itemDataVO.getItemType())
                || PaperItemTypeCode.ITEM_TYPE_CLOZE_FILL_IN_BLANKS_CHILD_CODE.getCode().equalsIgnoreCase(itemDataVO.getItemType())){
            //不定项选择题暂不参加选项乱序
            this.mixOption(itemDataVO, candidateAnswer, optionMixFlag);
        }
        return candidateAnswer;
    }


    private void setForceItem(String examId, String candidateId, String paperId, String paperVersion,
                              TopicDataVO topicDataVO, ItemDataVO itemDataVO, List<ForceItem> forceItemList) {
        itemDataVO.getChildItemList().forEach(item -> {
            ForceItem forceItem = new ForceItem();
            forceItem.setId(UUIDUtils.randomUUID());
            forceItem.setExamId(examId);
            forceItem.setCandidateId(candidateId);
            forceItem.setPaperId(paperId);
            forceItem.setPaperVersion(paperVersion);
            forceItem.setTopicId(topicDataVO.getTopicId());
            forceItem.setItemId(item.getItemId());
            forceItem.setParentItemId(itemDataVO.getItemId());
            forceItem.setDisturbOption(item.getItemContent());
            forceItem.setSecondEditOption(item.getAnswer());
            forceItem.setSecondViewOption(item.getDescription());
            forceItemList.add(forceItem);
        });
    }

    /**
     * @param examId
     * @param candidateId
     * @param admissionNumber
     * @param score           分值
     * @param answer          标答
     * @param itemDataVO
     * @return
     */
    private CandidateAnswer setBaseCandidateAnswer(String examId, String candidateId, String admissionNumber,
                                                   String itemId, String parentItemId, String score, String answer,
                                                   ItemDataVO itemDataVO) {
        CandidateAnswer candidateAnswer = CandidateAnswer.builder().examId(examId).candidateId(candidateId)
                .itemType(itemDataVO.getItemType()).flagState(1).fullScore(score).itemId(itemId)
                .itemSerialNumber(itemDataVO.getItemSerialNumber()).topicId(itemDataVO.getTopicId())
                .itemDifficultyDegree(itemDataVO.getDifficulty()).objectiveOrSubjective(itemDataVO.getObjective())
                .admissionNumber(admissionNumber).parentItemId(parentItemId).standardAnswer(answer)
                .outTimeSecond((Objects.isNull(itemDataVO.getOutTimeSecond()) ? 0 : itemDataVO.getOutTimeSecond() )* 1000L)
                .build();
        candidateAnswer.setId(UUIDUtils.randomUUID());
        return candidateAnswer;
    }

    private final Lock lock = new ReentrantLock(true);

    /**
     * 保存考生信息
     *
     * @param examId
     * @param candidateId
     * @param candidatePaperInfo
     */
    private void saveCandidateProcess(String examId, String candidateId, CandidatePaperInfo candidatePaperInfo) {
        //更新试卷代码插入数据库
        CandidateProcess candidateProcess = new CandidateProcess();
        candidateProcess.setExamId(examId);
        candidateProcess.setCandidateId(candidateId);
        candidateProcess.setPaperId(candidatePaperInfo.getPaperId());
        candidateProcess.setPaperVersion(candidatePaperInfo.getPaperVersion());
        candidateProcess.setExamStatus(CandidateStatusCode.TESTING.getCode());
        candidateProcess.setPaperMode(candidatePaperInfo.getPaperMode());
        candidateProcess.setPaperScore(candidatePaperInfo.getPaperScore());
        candidateProcess.setPaperTimeLength(candidatePaperInfo.getAnswerTime());
        //更改考生过程表
        candidateProcessMapper.updatePaperInfo(candidateProcess);
    }

    /**
     * 保存考生信息 --- 异步先初始化
     *
     * @param examId
     * @param candidateId
     * @param candidatePaperInfo
     */
    private void saveCandidateProcessInitData(String examId, String candidateId, CandidatePaperInfo candidatePaperInfo,
                                              String admissionNumber) {
        //更新试卷代码插入数据库
        CandidateProcess candidateProcess = new CandidateProcess();
        candidateProcess.setExamId(examId);
        candidateProcess.setCandidateId(candidateId);
        candidateProcess.setPaperId(candidatePaperInfo.getPaperId());
        candidateProcess.setPaperVersion(candidatePaperInfo.getPaperVersion());
        candidateProcess.setExamStatus(CandidateStatusCode.UN_LOGIN.getCode());
        candidateProcess.setPaperMode(candidatePaperInfo.getPaperMode());
        candidateProcess.setPaperScore(candidatePaperInfo.getPaperScore());
        candidateProcess.setPaperTimeLength(candidatePaperInfo.getAnswerTime());
        candidateProcess.setLoginCount(0);
        candidateProcess.setAdmissionNumber(admissionNumber);
        candidateProcess.setId(UUIDUtils.randomUUID());
        candidateProcess.setPasswordNumber(StringUtil.encrypt(admissionNumber));
        candidateProcess.setCreateDate(LocalDateTime.now());
        candidateProcess.setUpdateDate(LocalDateTime.now());
        // 更改考生过程表
        candidateProcessMapper.insert(candidateProcess);
    }

    private String saveCandidatePaper(String examId, String candidateId, String admissionNumber,
                                      PaperDataVO paperDataVO, int sort) {
        CandidatePaper candidatePaper = new CandidatePaper();
        long answerTime = Long.parseLong(paperDataVO.getAnswerTime()) * 60 * 1000;
        candidatePaper.setId(UUIDUtils.randomUUID());
        candidatePaper.setCandidateId(candidateId);
        candidatePaper.setAdmissionNumber(admissionNumber);
        candidatePaper.setExamId(examId);
        candidatePaper.setFinish(false);
        candidatePaper.setMixSort(sort);
        candidatePaper.setSort(sort);
        candidatePaper.setPaperId(paperDataVO.getPaperId());
        candidatePaper.setPaperVersion(paperDataVO.getPaperVersion().toString());
        candidatePaper.setPaperType(paperDataVO.getPaperType());
        candidatePaper.setHandInTime(Long.parseLong(paperDataVO.getHandInTime()));
        candidatePaper.setPaperTimeLength(answerTime);
        candidatePaper.setRemainingTime(answerTime);
        candidatePaper.setExamUseTime(0L);
        candidatePaper.setPaperScore(paperDataVO.getPaperScore());
        iCandidatePaperService.save(candidatePaper);
        // ======begin===特殊处理======
        String key = KeyDefineCommon.getCandidatePaperKey(examId, candidateId, candidatePaper.getMixSort());
        // 提前初始化，放缓存，适用于获取小题，保存答案，获取性格测试卷分页题接口，只用部分字段，保证这部分字段不改变，不可直接修改数据库
        CandidatePaper cacheObj = new CandidatePaper();
        cacheObj.setId(candidatePaper.getId());
        cacheObj.setPaperType(candidatePaper.getPaperType());
        cacheObj.setPaperId(candidatePaper.getPaperId());
        cacheObj.setPaperVersion(candidatePaper.getPaperVersion());
        redisTemplate.opsForValue().set(key, JSON.toJSONString(cacheObj), 3, TimeUnit.DAYS);
        //========end===特殊处理=====
        return candidatePaper.getId();
    }



}
